/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.scripting.xmltags;

import java.util.Map;

import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.parsing.TokenHandler;
import org.apache.ibatis.session.Configuration;

/**
 * @author Clinton Begin
 */
/**
 * foreach SQL节点 TODO
 */
public class ForEachSqlNode implements SqlNode {
	public static final String ITEM_PREFIX = "__frch_";
	/**
	 * 表达式求值器
	 */
	private ExpressionEvaluator evaluator;
	/**
	 * 集合表达式
	 */
	private String collectionExpression;
	/**
	 * sql内容
	 */
	private SqlNode contents;
	/**
	 * 前缀
	 */
	private String open;
	/**
	 * 后缀
	 */
	private String close;
	/**
	 * 分隔符，表示迭代时每个元素之间以什么分隔 ，我们通常可以将之用到批量删除、添加等操作中。
	 */
	private String separator;
	/**
	 * 表示在迭代过程中每一个元素的别名
	 */
	private String item;
	/**
	 * 表示在迭代过程中每次迭代到的位置（下标）
	 */
	private String index;
	/**
	 * 配置
	 */
	private Configuration configuration;

	/**
	 * 初始化for each SQL节点
	 * 
	 * @param configuration
	 * @param contents
	 * @param collectionExpression
	 * @param index
	 * @param item
	 * @param open
	 * @param close
	 * @param separator
	 */
	public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index,
			String item, String open, String close, String separator) {
		this.evaluator = new ExpressionEvaluator();
		this.collectionExpression = collectionExpression;
		this.contents = contents;
		this.open = open;
		this.close = close;
		this.separator = separator;
		this.index = index;
		this.item = item;
		this.configuration = configuration;
	}

	@Override
	public boolean apply(DynamicContext context) {
		Map<String, Object> bindings = context.getBindings();
		// 将Map/Array/List统一包装为迭代器接口
		// 解析collectionExpression->iterable,核心用的ognl
		final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
		if (!iterable.iterator().hasNext()) {
			return true;
		}
		boolean first = true;
		// 加上(
		applyOpen(context);
		int i = 0;
		for (Object o : iterable) {
			DynamicContext oldContext = context;
			if (first) {
				context = new PrefixedContext(context, "");
			} else if (separator != null) {
				context = new PrefixedContext(context, separator);
			} else {
				context = new PrefixedContext(context, "");
			}
			int uniqueNumber = context.getUniqueNumber();
			// Issue #709
			if (o instanceof Map.Entry) { //Map条目处理
				@SuppressWarnings("unchecked")
				Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
				applyIndex(context, mapEntry.getKey(), uniqueNumber);
				applyItem(context, mapEntry.getValue(), uniqueNumber);
			} else { // List条目处理
				// 索引
				applyIndex(context, i, uniqueNumber);
				// 加上一个元素
				applyItem(context, o, uniqueNumber);
			}
			 // 子节点SqlNode处理，很重要的一个逻辑就是将#{item.XXX}转换为#{__frch_item_N.XXX}，这样在JDBC设置参数的时候就能够找到对应的参数值了
			contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
			if (first) {
				first = !((PrefixedContext) context).isPrefixApplied();
			}
			context = oldContext;
			i++;
		}
		// 加上)
		applyClose(context);
		return true;
	}

	private void applyIndex(DynamicContext context, Object o, int i) {
		if (index != null) {
			context.bind(index, o);
			context.bind(itemizeItem(index, i), o);
		}
	}

	private void applyItem(DynamicContext context, Object o, int i) {
		if (item != null) {
			context.bind(item, o);
			context.bind(itemizeItem(item, i), o);
		}
	}

	/**
	 * 给context添加前缀
	 * 
	 * @param context
	 */
	private void applyOpen(DynamicContext context) {
		// 如果前缀不为空
		if (open != null) {
			// 添加open
			context.appendSql(open);
		}
	}
	/**
	 * 给context添加后缀
	 * 
	 * @param context
	 */
	private void applyClose(DynamicContext context) {
		// 如果后缀不为空
		if (close != null) {
			context.appendSql(close);
		}
	}
	 /**
	  * 详细说明在迭代过程中每一个元素的别名
	  * @param item 在迭代过程中每一个元素的别名
	  * @param i 下标
	  * @return
	  */
	private static String itemizeItem(String item, int i) {
		return new StringBuilder(ITEM_PREFIX).append(item).append("_").append(i).toString();
	}

	/**
	 *  被过滤的动态上下文
	 * @author 
	 *
	 */
	private static class FilteredDynamicContext extends DynamicContext {
		/**
		 * 代表
		 */
		private DynamicContext delegate;
		/**
		 * 下标
		 */
		private int index;
		/**
		 * 元素集下标
		 */
		private String itemIndex;
		/**
		 * 元素集
		 */
		private String item;

		public FilteredDynamicContext(Configuration configuration, DynamicContext delegate, String itemIndex,
				String item, int i) {
			super(configuration, null);
			this.delegate = delegate;
			this.index = i;
			this.itemIndex = itemIndex;
			this.item = item;
		}

		@Override
		public Map<String, Object> getBindings() {
			return delegate.getBindings();
		}

		@Override
		public void bind(String name, Object value) {
			delegate.bind(name, value);
		}

		@Override
		public String getSql() {
			return delegate.getSql();
		}

		@Override
		public void appendSql(String sql) {
			GenericTokenParser parser = new GenericTokenParser("#{", "}", new TokenHandler() {
				 // 将#{item.XXX}转换为#{__frch_item_N.XXX}
				@Override
				public String handleToken(String content) {
					String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])",
							itemizeItem(item, index));
					if (itemIndex != null && newContent.equals(content)) {
						newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])",
								itemizeItem(itemIndex, index));
					}
					return new StringBuilder("#{").append(newContent).append("}").toString();
				}
			});

			delegate.appendSql(parser.parse(sql));
		}

		@Override
		public int getUniqueNumber() {
			return delegate.getUniqueNumber();
		}

	}

	/**
	 * 前缀上下文
	 * 
	 * @author
	 *
	 */
	private class PrefixedContext extends DynamicContext {
		/**
		 * 代表
		 */
		private DynamicContext delegate;
		/**
		 * 前缀
		 */
		private String prefix;
		/**
		 * 前缀是否应用
		 */
		private boolean prefixApplied;
		/**
		 * 初始化
		 * @param delegate 代表
		 * @param prefix 前缀
		 */
		public PrefixedContext(DynamicContext delegate, String prefix) {
			super(configuration, null);
			this.delegate = delegate;
			this.prefix = prefix;
			this.prefixApplied = false;
		}
		/**
		 * 前缀是否应用
		 * @return
		 */
		public boolean isPrefixApplied() {
			return prefixApplied;
		}

		@Override
		public Map<String, Object> getBindings() {
			return delegate.getBindings();
		}

		@Override
		public void bind(String name, Object value) {
			delegate.bind(name, value);
		}

		@Override
		public void appendSql(String sql) {
			//判断前缀是否可以应用并同时判断sql不为空
			if (!prefixApplied && sql != null && sql.trim().length() > 0) {
				delegate.appendSql(prefix);
				prefixApplied = true;
			}
			delegate.appendSql(sql);
		}

		@Override
		public String getSql() {
			return delegate.getSql();
		}

		@Override
		public int getUniqueNumber() {
			return delegate.getUniqueNumber();
		}
	}

}
